#include "SRModel.h"
#include "SRData.h"

#include <cassert>

using std::vector;
using matsu::vec3;
using std::auto_ptr;

namespace sora
{
	MeshModel::MeshModel(const vector<MeshData*> dataList)
	: dataList_(dataList)
	{
	}
	MeshModel::~MeshModel()
	{
		vector<MeshData*>::iterator iter;
		for(iter = dataList_.begin() ; iter != dataList_.end() ; iter++)
		{
			MeshData *data = *iter;
			delete(data);
		}
	}
	ModelType MeshModel::type() const { return ModelTypeMesh; }
	const std::vector<DrawCommand> MeshModel::getDrawCommand() const
	{
		vector<DrawCommand> cmdList;
		vector<MeshData*>::const_iterator iter;
		for(iter = dataList_.begin() ; iter != dataList_.end() ; iter++)
		{
			MeshData *data = *iter;
			const std::vector<DrawCommand> childCmdList = data->getDrawCommand();
			for(std::size_t i = 0 ; i < childCmdList.size() ; i++)
			{
				const DrawCommand &cmd = childCmdList[i];
				cmdList.push_back(cmd);
			}
		}
		return cmdList;
	}

	WireFrameModel::WireFrameModel(const MeshModel &mesh)
	{
		//mesh -> wireframe
		const vector<MeshData*> &datalist = mesh.dataList_;
		vector<MeshData*>::const_iterator iter;
		for(iter = datalist.begin() ; iter != datalist.end() ; iter++)
		{
			MeshData *data = *iter;
			WireFrameData *wireframe = new WireFrameData(*data);
			dataList_.push_back(wireframe);
		}
	}
	WireFrameModel::~WireFrameModel()
	{
		vector<WireFrameData*>::iterator iter;
		for(iter = dataList_.begin() ; iter != dataList_.end() ; iter++)
		{
			WireFrameData *data = *iter;
			delete(data);
		}
	}
	ModelType WireFrameModel::type() const { return ModelTypeWireframe; }
	const std::vector<DrawCommand> WireFrameModel::getDrawCommand() const
	{
		vector<DrawCommand> cmdList;
		vector<WireFrameData*>::const_iterator iter;
		for(iter = dataList_.begin() ; iter != dataList_.end() ; iter++)
		{
			WireFrameData *data = *iter;
			vector<DrawCommand> childCmdList = data->getDrawCommand();
			//TODO: 합치는거 stl기반으로 변경
			for(std::size_t i = 0 ; i < childCmdList.size() ; i++)
			{
				DrawCommand &cmd = childCmdList[i];
				cmdList.push_back(cmd);
			}
		}
		return cmdList;
	}

#if _IPHONE_
#pragma mark bounding box
#endif

	BoundingBox::BoundingBox(const Box &box)
	: box_(new Box(box))
	{
		init();
	}
	BoundingBox::BoundingBox(const BoundingBox &box)
	: box_(new Box(*box.box_))
	{
		init();
	}
	BoundingBox::BoundingBox(float top, float bottom, float left, float right, float front, float back)
	: box_(new Box(top, bottom, left, right, front, back))
	{
		init();
	}
	BoundingBox::BoundingBox(Model *model)
	{
		Box b = model2box(model);
		box_ = auto_ptr<Box>(new Box(b));
		init();
	}
	BoundingBox::BoundingBox(ModelPtr model)
	{
		Box b = model2box(model.get());
		box_ = auto_ptr<Box>(new Box(b));
		init();
	}
	Box BoundingBox::model2box(Model *model)
	{
		//else...
		float left = FLT_MAX;
		float right = -FLT_MAX;
		float top = -FLT_MAX;
		float bottom = FLT_MAX;
		float front = -FLT_MAX;
		float back = FLT_MAX;

		std::vector<DrawCommand> cmdList = model->getDrawCommand();
		std::vector<DrawCommand>::iterator iter;
		for(iter = cmdList.begin() ; iter != cmdList.end() ; iter++)
		{
			const DrawCommand &cmd = *iter;
			const VertexPointer &ptr = cmd.pointer();
			int vertexCount = ptr.count();
			for(int i = 0 ; i < vertexCount ; i++)
			{
				char *start = (char*)ptr.start();
				int offset = (int)ptr.vertexOffset();
				int indexoffset = ptr.stride() * i;
				char *vertexPtr = start + indexoffset + offset;

				float *vertex = (float*)vertexPtr;
				float x = vertex[0];
				float y = vertex[1];
				float z = vertex[2];

				if(x < left)
					left = x;
				else if(x > right)
					right = x;

				if(y < bottom)
					bottom = y;
				else if(y > top)
					top = y;

				if(z > front)
					front = z;
				else if(z < back)
					back = z;
			}
		}

		//box구성
		return Box(top, bottom, left, right, front, back);
	}

	ModelType BoundingBox::type() const { return ModelTypeBoundingBox; }

	void BoundingBox::init()
	{
		//top
		//0 1
		//2 3
		//bottom
		//4 5
		//6 7

		vec3 p0(box_->left(), box_->top(), box_->back());
		vec3 p1(box_->right(), box_->top(), box_->back());
		vec3 p2(box_->left(), box_->top(), box_->front());
		vec3 p3(box_->right(), box_->top(), box_->front());
		vec3 p4(box_->left(), box_->bottom(), box_->back());
		vec3 p5(box_->right(), box_->bottom(), box_->back());
		vec3 p6(box_->left(), box_->bottom(), box_->front());
		vec3 p7(box_->right(), box_->bottom(), box_->front());

		SimpleVertex3 v0(p0);
		SimpleVertex3 v1(p1);
		SimpleVertex3 v2(p2);
		SimpleVertex3 v3(p3);
		SimpleVertex3 v4(p4);
		SimpleVertex3 v5(p5);
		SimpleVertex3 v6(p6);
		SimpleVertex3 v7(p7);

		vertexList_.push_back(v0);
		vertexList_.push_back(v1);
		vertexList_.push_back(v2);
		vertexList_.push_back(v3);
		vertexList_.push_back(v4);
		vertexList_.push_back(v5);
		vertexList_.push_back(v6);
		vertexList_.push_back(v7);

		const GLushort indexData[] = {
			0, 1,	1, 3,	3, 2,	2, 0,
			4, 5,	5, 7,	7, 6,	6, 4,
			0, 4,	2, 6,	3, 7,	1, 5
		};
		indexList_ = vector<GLushort>(indexData, indexData + sizeof(indexData)/sizeof(GLushort));
	}

	float BoundingBox::width() const { return (box_->right() - box_->left()); }
	float BoundingBox::height() const { return (box_->top() - box_->bottom()); }
	float BoundingBox::depth() const { return (box_->front() - box_->back()); }

	const std::vector<DrawCommand> BoundingBox::getDrawCommand() const
	{
		VertexPointerBuilder builder;
		builder.setStartPointer(&vertexList_[0].pos[0]);
		builder.setVertexOffset(0);
		builder.setStride(sizeof(vertexList_[0]));
		builder.setCount(vertexList_.size());

		VertexPointer ptr;
		builder.build(ptr);

		GLsizei count = indexList_.size();
		const GLvoid *indices = &indexList_[0];

		vector<DrawCommand> cmdList;
		DrawCommand cmd(GL_LINES, count, GL_UNSIGNED_SHORT, indices, ptr);
		cmdList.push_back(cmd);
		return cmdList;
	}

#if _IPHONE_
#pragma mark mesh data
#endif
	MeshData::MeshData(GLenum mode, const std::vector<ModelVertex3> &vertexArray,
			 const std::vector<GLushort> &indexArray,
			 bool useTex, bool useNormal)
	: mode_(mode), vertexArray_(vertexArray), indexArray_(indexArray), useTex_(useTex), useNormal_(useNormal)
	{
		SR_ASSERT(mode == GL_TRIANGLES || mode == GL_TRIANGLE_FAN || mode == GL_TRIANGLE_STRIP);	//wireframe은 선구로 구성되어야한다
	}
	ModelType MeshData::type() const { return ModelTypeMeshData; }
	GLenum MeshData::mode() const { return mode_; }
	const std::vector<ModelVertex3> &MeshData::vertexArray() const { return vertexArray_; }
	const std::vector<GLushort> &MeshData::indexArray() const { return indexArray_; }
	bool MeshData::useTex() const { return useTex_; }
	bool MeshData::useNormal() const { return useNormal_; }

	const std::vector<DrawCommand> MeshData::getDrawCommand() const
	{
		VertexPointerBuilder builder;

		const ModelVertex3 &first = vertexArray_[0];
		builder.setStartPointer(&first.pos[0]);
		builder.setVertexOffset(0);
		builder.setStride(sizeof(first));
		builder.setCount(vertexArray_.size());

		if(useTex() == true)
		{
			int offset = builder.calcOffset<float>(&first.texCoord[0], &first.pos[0]);
			builder.setTexCoordOffset(offset);
		}
		if(useNormal() == true)
		{
			int offset = builder.calcOffset<float>(&first.normal[0], &first.pos[0]);
			builder.setNormalOffset(offset);
		}

		VertexPointer ptr;
		builder.build(ptr);

		GLsizei count = indexArray_.size();
		const GLvoid *indices = &indexArray_[0];

		vector<DrawCommand> cmdList;
		DrawCommand cmd(mode(), count, GL_UNSIGNED_SHORT, indices, ptr);
		cmdList.push_back(cmd);
		return cmdList;
	}

#if _IPHONE_
#pragma mark wire data
#endif
	WireFrameData::WireFrameData(const MeshData &mesh)
	: mode_(GL_LINES)
	{
		const vector<ModelVertex3> &meshVertexList = mesh.vertexArray();
		const vector<GLushort> &meshIndexList = mesh.indexArray();
		SR_ASSERT(mesh.mode() == GL_TRIANGLES);

		//vertex3개로 구성된 하나의 삼각형을 lines로 풀어서 구성한다
		//vertex data에서 좌표정보만 뺴서 새로운 vertex list구성
		vector<ModelVertex3>::const_iterator iter;
		for(iter = meshVertexList.begin() ; iter != meshVertexList.end() ; iter++)
		{
			const ModelVertex3 &v = *iter;
			const SimpleVertex3 vertex(&v.pos[0]);
			vertexList_.push_back(vertex);
		}

		const int numTriangle = meshIndexList.size() / 3;
		for(int i = 0 ; i < numTriangle ; i++)
		{
			const int start = i * 3;
			const int index1 = meshIndexList[start];
			const int index2 = meshIndexList[start + 1];
			const int index3 = meshIndexList[start + 2];

			indexList_.push_back(index1);
			indexList_.push_back(index2);

			indexList_.push_back(index2);
			indexList_.push_back(index3);

			indexList_.push_back(index3);
			indexList_.push_back(index1);
		}
	}

	ModelType WireFrameData::type() const { return ModelTypeWireFrameData; }

	GLenum WireFrameData::mode() const { return mode_; }
	const std::vector<SimpleVertex3> &WireFrameData::vertexList() const { return vertexList_; }
	const std::vector<GLushort> &WireFrameData::indexList() const { return indexList_; }
	const std::vector<DrawCommand> WireFrameData::getDrawCommand() const
	{
		VertexPointerBuilder builder;

		const SimpleVertex3 &first = vertexList_[0];
		builder.setStride(sizeof(first));
		builder.setStartPointer(&first.pos[0]);
		builder.setVertexOffset(0);
		builder.setCount(vertexList_.size());

		VertexPointer ptr;
		builder.build(ptr);

		GLsizei count = indexList_.size();
		const GLvoid *indices = &indexList_[0];

		vector<DrawCommand> cmdList;
		DrawCommand cmd(mode(), count, GL_UNSIGNED_SHORT, indices, ptr);
		cmdList.push_back(cmd);
		return cmdList;
	}

#if _IPHONE_
#pragma mark builder
#endif
	void MeshModelBuilder::addMesh(GLenum mode, const std::vector<ModelVertex3> &vertexList,
								  const std::vector<GLushort> &indexList,
								  bool useTex, bool useNormal)
	{
		MeshData *data = new MeshData(mode, vertexList, indexList, useTex, useNormal);
		dataList_.push_back(data);
	}
	Model* MeshModelBuilder::build()
	{
		return new MeshModel(dataList_);
	}

}
